package com.mdroid.support.widgets;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.Scroller;

import com.mdroid.R;

/**
 * @author William.cheng
 */
public class DrawerLayout extends RelativeLayout {
    private final String TAG = DrawerLayout.this.getClass().getSimpleName();
    
    private static final int SNAP_VELOCITY = 1000;
    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;
    private final static int TOUCH_STATE_VSCROLLING = 2;
    
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private int mTouchState = TOUCH_STATE_REST;
    private float mLastMotionX;
    private float mLastMotionY;
    private int mTouchSlop;
    private int mMaximumVelocity;
    
    private DrawerFrontLayout mFrontView;
    private View mFrontBackground;
    private ViewGroup mBackgroundContent;
    private Handler mHandler;
    private int mFrontBackgroundMargin;
    private int mMinLeftMargin;
    private int mMaxLeftMargin;
    private boolean mMoving, mShake = true;

    private boolean mAllowRight = true;
    private boolean mAllowLeft = true;
    
    public DrawerLayout(Context context) {
        super(context);
        init(context, null);
    }

    public DrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }
    
    private void init(Context context, AttributeSet attrs) {
        WindowManager windowManager = ((Activity)context).getWindowManager();
        Display display = windowManager.getDefaultDisplay();
        int screenWidth = display.getWidth();
        
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawerLayout);
        int leftKeepWidth = a.getDimensionPixelSize(R.styleable.DrawerLayout_leftKeepWidth, 60);
        int rightKeepWidth = a.getDimensionPixelSize(R.styleable.DrawerLayout_rightKeepWidth, 60);
        a.recycle();
        
        mMinLeftMargin = (int) (leftKeepWidth - screenWidth);
        mMaxLeftMargin = (int) (screenWidth - rightKeepWidth);
        
        mScroller = new Scroller(getContext());
        final ViewConfiguration configuration = ViewConfiguration
                .get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        
        
        mHandler = new Handler();
    }
    
    @Override
    protected void onFinishInflate() {
        mFrontView = (DrawerFrontLayout) findViewById(R.id.front_view);
        if (mFrontView == null) {
            throw new IllegalArgumentException("Must has the FrontLayout that id is R.id.front_view");
        }
        mBackgroundContent = (ViewGroup) findViewById(R.id.background_layout);
        if (mBackgroundContent == null) {
            throw new IllegalArgumentException("Must has the View that id is R.id.background_layout");
        }
        mFrontBackground = findViewById(R.id.front_bg);
        mFrontBackgroundMargin = ((LayoutParams)mFrontBackground.getLayoutParams()).leftMargin;
        
        mFrontView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mMoving && mFrontStatus != Status.CENTER) {
                    flyToCenter();
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0 || mMoving)
            return false;
        final int action = ev.getAction();
        if (action == MotionEvent.ACTION_MOVE && mTouchState == TOUCH_STATE_VSCROLLING) {
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final float x = ev.getX();
        final float y = ev.getY();

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
             * will be false if being flinged.
             */
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }

            // Remember where the motion event started
            mLastMotionX = x;
            mLastMotionY = y;

            mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
                    : TOUCH_STATE_SCROLLING;

            break;

        case MotionEvent.ACTION_MOVE:
            final int xDiff = (int) Math.abs(x - mLastMotionX);
            final int yDiff = (int) Math.abs(y - mLastMotionY);
            boolean xMoved = false;
            boolean yMoved = false;
            
            if (xDiff > yDiff) {
                xMoved = xDiff > mTouchSlop;
            } else {
                yMoved = yDiff > mTouchSlop;
            }
            
            if (yMoved) {
                mTouchState = TOUCH_STATE_VSCROLLING;
                return false;
            }

            if (xMoved) {
                // Scroll if the user moved far enough along the X axis
                mTouchState = TOUCH_STATE_SCROLLING;
            }

            if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                //final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;
                mLastMotionY = y;

                /*final int scrollX = getScrollX();
                if (deltaX < 0) {
                    if (scrollX > 0) {
                        scrollBy(Math.max(-scrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(
                            getChildCount() - 1).getRight()
                            - scrollX - getWidth();
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }*/
                return true;
            }
            break;

        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                /*final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1);
                } else if (velocityX < -SNAP_VELOCITY
                        && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1);
                } else {
                    snapToDestination();
                }*/

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
            }

            mTouchState = TOUCH_STATE_REST;

            break;
        case MotionEvent.ACTION_CANCEL:
            mTouchState = TOUCH_STATE_REST;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0)
            return false;

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        final float x = ev.getX();
        final float y = ev.getY();

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
             * will be false if being flinged.
             */
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }

            // Remember where the motion event started
            mLastMotionX = x;
            mLastMotionY = y;

            mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
                    : TOUCH_STATE_SCROLLING;

            break;

        case MotionEvent.ACTION_MOVE:
            final int xDiff = (int) Math.abs(x - mLastMotionX);

            boolean xMoved = xDiff > mTouchSlop;

            if (xMoved) {
                // Scroll if the user moved far enough along the X axis
                mTouchState = TOUCH_STATE_SCROLLING;
            }

            if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                final int deltaX = (int) (x - mLastMotionX);
                mLastMotionX = x;
                mLastMotionY = y;
                int tempMargin = ((LayoutParams)mFrontView.getLayoutParams()).leftMargin + deltaX;
                if (mFrontStatus == Status.CENTER && ((tempMargin > 0 && !mAllowRight) || (tempMargin < 0 && !mAllowLeft))) {
                    return true;
                }

                performMovingBy(deltaX);
                return true;
            }
            break;

        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && (mAllowRight || mFrontStatus != Status.CENTER)) {//往右滑.显示左边的menu
                 // Fling hard enough to move left
                    switch (mFrontStatus) {
                    case LEFT:
                        flyToCenter();
                        break;
                    case CENTER:
                        flyToRight();
                        break;
                    case RIGHT:
                        break;
                    }
                } else if (velocityX < -SNAP_VELOCITY && (mAllowLeft || mFrontStatus != Status.CENTER)) {//往左滑,显示右边的menu
                    // Fling hard enough to move right
                    switch (mFrontStatus) {
                    case LEFT:
                        break;
                    case CENTER:
                        flyToLeft();
                        break;
                    case RIGHT:
                        flyToCenter();
                        break;
                    }
                } else {
                    snapToDestination();
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
            }

            mTouchState = TOUCH_STATE_REST;

            break;
        case MotionEvent.ACTION_CANCEL:
            snapToDestination();
            mTouchState = TOUCH_STATE_REST;
        }
        return true;
    }
    
    private static final int SHAKE_VALUE = 5;
    /**
     * @author William.cheng
     * @param x 要移动到的位置
     */
    private float performFling(final int x, final boolean shake) {
        final int o = ((LayoutParams) mFrontView.getLayoutParams()).leftMargin;
        int temp = x - o;
        if (temp == 0) {
            return 0f;
        }
        if (temp > 0 && shake) {
            temp += SHAKE_VALUE;
        } else if (temp < 0 && shake) {
            temp -= SHAKE_VALUE;
        }
        final int pos = temp;
        mMoving = true;
        //fling start
        final float durationMs = Math.min(300f, (300f * Math.abs(temp)) / 480f);
        final long startTime = System.currentTimeMillis();
        mHandler.post(new Runnable() {
            public void run() {
                long now = System.currentTimeMillis();
                float currentMs = Math.min(durationMs, now - startTime);
                float t = Math.min(1f, currentMs / durationMs);
                float delatX = pos * t;
                performMoving((int) (o + delatX));

                if (currentMs < durationMs) {
                    mHandler.post(this);
                } else if (shake) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            performMoving(x);
                            flied(x);
                        }
                    });
                } else {
                    flied(x);
                }
            }
        });
        return durationMs;
    }
    
    private void flied(int x) {
        mMoving = false;
        mFrontStatus = x == 0 ? Status.CENTER : x < 0 ? Status.LEFT : Status.RIGHT;
        statusChanged();
    }

    public void performMovingBy(int deltaX) {
        LayoutParams params = (LayoutParams) mFrontView.getLayoutParams();
        int x = params.leftMargin + deltaX;
        if (x > mMaxLeftMargin) {
            x = mMaxLeftMargin;
        } else if (x < mMinLeftMargin) {
            x = mMinLeftMargin;
        }
        performMoving(x);
    }
    
    /**
     * {@link #mFrontStatus}
     */
    private Status mBackgroundStatus = Status.CENTER;
    public void performMoving(int x) {
        LayoutParams params = (LayoutParams) mFrontView.getLayoutParams();
        params.leftMargin = x;
        params.rightMargin = -x;
        
        if (mStatusChange != null) {
            if (params.leftMargin > SHAKE_VALUE && mBackgroundStatus != Status.RIGHT) {
                mBackgroundStatus = Status.RIGHT;
                mStatusChange.onChange(this, mBackgroundStatus);
            } else if (params.leftMargin < -SHAKE_VALUE && mBackgroundStatus != Status.LEFT) {
                mBackgroundStatus = Status.LEFT;
                mStatusChange.onChange(this, mBackgroundStatus);
            } else if (params.leftMargin == 0 && mBackgroundStatus != Status.CENTER) {
                mBackgroundStatus = Status.CENTER;
                mStatusChange.onChange(this, mBackgroundStatus);
            }
        }
        
        mFrontView.setLayoutParams(params);
        if (mFrontBackground != null) {
            LayoutParams bgParams = (LayoutParams) mFrontBackground.getLayoutParams();
            bgParams.setMargins(x + mFrontBackgroundMargin, 0, -x + mFrontBackgroundMargin, 0);
            mFrontBackground.setLayoutParams(bgParams);
        }
    }
    
    /**
     * 三种状态  0:中间 1:右边 -1:左边
     */
    private Status mFrontStatus = Status.CENTER;
    private void snapToDestination() {
        int leftMargin = ((LayoutParams)mFrontView.getLayoutParams()).leftMargin;
        final int width = getWidth();
        final int w = width / 3;
        switch (mFrontStatus) {
        case LEFT:
            if (leftMargin + width < w) {
                flyToLeft();
            } else {
                flyToCenter();
            }
            break;
        case CENTER:
            if (leftMargin > w) {
                flyToRight();
            } else if(leftMargin < -w){
                flyToLeft();
            } else {
                flyToCenter();
            }
            break;
        case RIGHT:
            if (leftMargin - width > -w) {
                flyToRight();
            } else {
                flyToCenter();
            }
            break;
        }
    }
    
    private OnStatusChange mStatusChange;
    public interface OnStatusChange {
        /**
         * @author William.cheng
         * @param status 将要切换到的状态
         */
        void onChange(DrawerLayout drawer, Status status);
    }
    
    private void statusChanged() {
        mFrontView.setStatus(mFrontStatus);
    }
    
    public void setOnStatusChanged(OnStatusChange onStatusChange) {
        mStatusChange = onStatusChange;
    }
    
    public void setBackgroundView(View v) {
        Log.v(TAG, "setBackgroundView");
        mBackgroundContent.removeAllViews();
        mBackgroundContent.addView(v);
    }
    
    public void removeBackgroundViews() {
        Log.v(TAG, "removeBackgroundViews");
        mBackgroundContent.removeAllViews();
    }
    
    public void setAllowToRight(boolean allow) {
        mAllowRight = allow;
    }
    
    public void setAllowToLeft(boolean allow) {
        mAllowLeft = allow;
    }
    
    public enum Status {
        LEFT, CENTER, RIGHT
    }

    public void flyToLeft() {
        if (mAllowLeft) {
            performFling(mMinLeftMargin, mShake);
        }
    }
    
    public void flyToRight() {
        if (mAllowRight) {
            performFling(mMaxLeftMargin, mShake);
        }
    }
    
    public void flyToCenter() {
        performFling(0, mShake);
    }
    
    public void changeData(Runnable runnable) {
        changeData(runnable, 500);
    }
    
    public void setShake(boolean shake) {
        mShake = shake;
    }
    
    public boolean isShake() {
        return mShake;
    }
    
    /**
     * 执行切换动画
     * @author William.cheng
     * @param runnable
     * @param delay
     */
    public void changeData(Runnable runnable, long delay) {
        if (mMoving) {
            return;
        }
        LayoutParams params = (LayoutParams) mFrontView.getLayoutParams();
        if (params.leftMargin == 0) {
            mHandler.post(runnable);
            return;
        }
        int x = params.leftMargin > 0 ? getWidth() : -getWidth();
        performFling(x, runnable, delay);
    }
    
    private float performFling(final int x, final Runnable runnable, final long delay) {
        final int o = ((LayoutParams) mFrontView.getLayoutParams()).leftMargin;
        int temp = x - o;
        if (temp == 0) {
            return 0f;
        }
        final int pos = temp;
        mMoving = true;
        //fling start
        final float durationMs = Math.min(300f, (300f * Math.abs(temp)) / 480f);
        final long startTime = System.currentTimeMillis();
        mHandler.post(new Runnable() {
            public void run() {
                long now = System.currentTimeMillis();
                float currentMs = Math.min(durationMs, now - startTime);
                float t = Math.min(1f, currentMs / durationMs);
                float delatX = pos * t;
                performMoving((int) (o + delatX));

                if (currentMs < durationMs) {
                    mHandler.post(this);
                } else {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            runnable.run();
                            mHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    flyToCenter();
                                }
                            }, delay);
                        }
                    });
                }
            }
        });
        return durationMs;
    }
}
